/*
* Copyright (c) 2006 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#ifndef M_COMPTR_H
#define M_COMPTR_H

#include "atomic.h"

#include "exception.h"

#ifdef _MSC_VER
#pragma warning(disable:4127)
#endif

namespace Engine
{

	/**
	*   Class for proxy reference counting object(array m_pointer).
	*/
	template<class T>
	class COMStorage
	{
    public:
        /**
		*   Explicit constructor.
        */
		COMStorage(T* p):m_pointer(p),m_count(0){}

		/**
		*   Destructor.
        */
        ~COMStorage(){Assert(m_count >= 0);}

		/**
		*   Increment reference counter.
        */
		void AddRef()
		{
			const_cast<T*>(m_pointer)->AddRef();
			AtomicIncrement(&m_count);
		}

		/**
		*   Decrement reference counter.
       	*   And destroy real object.
        */
        void DelRef()
        {
			const_cast<T*>(m_pointer)->Release();
			AtomicDecrement(&m_count);
            if(m_count == 0)
			{
				unsigned long refcount = const_cast<T*>(m_pointer)->Release();
				m_pointer = 0;
				Assert(refcount == 0);
			}
			Assert(m_count >= 0);
        }

		/**
		*   Operator return reference to real object.
        */
        T& operator*() {return const_cast<T&>(*m_pointer);}

		/**
		*   Operator return m_pointer to real object.
        */
        T* operator->(){return const_cast<T*>(m_pointer);}

		/**
		*   Operator return reference to real object.
        */
        const T& operator*()const {return *m_pointer;}

		/**
		*   Operator return m_pointer to real object.
        */
        const T* operator->()const{return m_pointer;}

		/**
		*   Return number of references.
        */
        int GetCount()const {return m_count;}

		/**
		*   Return true if object can be deleted, there are no pointers and weak references
		*/
		bool CanDelete(){return GetCount() == 0;}

		/**
		*   Return true if pointer is valid.
		*/
		bool IsNull(){return m_pointer == 0;}

	private:
        volatile T* m_pointer;///< m_pointer to real object.
        volatile long m_count;///< reference counter.

		/**
		*   hidden assignment operator.
		*/
        COMStorage<T>& operator=(const COMStorage<T>& rhs);

		/**
		*   hidden copy constructor.
		*/
        COMStorage(const COMStorage<T>& rhs);
    };



    /**
	*   Class that represent reference counting m_pointer.
	*/
    template<class T>
    class ComPtr
    {
    public:

        /**
		*   Default constructor.
		*/
        ComPtr():m_refCounter(0){}

		/**
		*   Constructor.
        *   Create new proxy object;
        */
        explicit ComPtr(T* ptr)
        {
			if(ptr != 0)
			{
#if defined(_DEBUG) && defined(_MSC_VER)
				m_refCounter = new(_CLIENT_BLOCK,__FILE__, __LINE__) COMStorage<T>(ptr);
#else
				m_refCounter = new COMStorage<T>(ptr);
#endif
				m_refCounter->AddRef();
			}
			else
				m_refCounter = 0;
        }

		/**
		*   Copy constructor.
        *   Copy proxy object.
        */
        ComPtr(const ComPtr<T>& aptr)
        {
            Copy(aptr);
        }

		/**
		*   Destructor.
        */
        ~ComPtr()
        {
            Delete();
        }

		/**
		*   Operator assign one m_pointer to another.
        *   First kill prev value,that assign new.
        */
        ComPtr<T>& operator=(const ComPtr<T>& rhs)
        {
            if(m_refCounter == rhs.m_refCounter)
                return *this;

			Delete();

            Copy(rhs);

            return *this;
        }

		/**
		*   Return reference to real object using proxy object operator.
        */
        T& operator*()
        {
            if(m_refCounter != 0)
                return m_refCounter->operator*();
			else
				throw std::logic_error("Not initialized m_pointer");
        }

		/**
		*   Return m_pointer to real object using proxy object operator.
        */
        T* operator->()
        {
            if(m_refCounter != 0)
                return m_refCounter->operator->();
			else
				std::logic_error("Not initialized m_pointer");
			return NULL;
        }

		/**
		*   Return reference to real object using proxy object operator.
        */
        const T& operator*()const
        {
            if(m_refCounter == 0)
				std::logic_error("Not initialized m_pointer");

			return m_refCounter->operator*();
        }

		/**
		*   Return m_pointer to real object using proxy object operator.
        */
        const T* operator->()const
        {
            if(m_refCounter == 0)
				std::logic_error("Not initialized m_pointer");
			return m_refCounter->operator->();
        }

		/**
		*   Return m_pointer to object
		*/
		T* GetPointer()
		{
            if(m_refCounter == 0)
				std::logic_error("Not initialized m_pointer");
			return m_refCounter->operator->();
		}

		/**
		*   Return m_pointer to object
		*/
		const T* GetPointer()const
		{
            if(m_refCounter == 0)
				std::logic_error("Not initialized m_pointer");
			return m_refCounter->operator->();
		}

		/**
		*   Return number of references.
        */
        int GetCount()const
		{
			if(m_refCounter != NULL)
				return m_refCounter->GetCount();
			return 0;
		};



		/**
		*   Test m_pointer is initialized or no.
        */
        bool IsNull()const
        {
            return m_refCounter == 0;
        }

		/**
		*   Delete value.
        *   Decrement reference, and destroy object.
        */
        void Delete()
        {
            if(m_refCounter != 0)
            {
                m_refCounter->DelRef();
                if(m_refCounter->CanDelete())
                    delete m_refCounter;
				m_refCounter = 0;
            }
        }

		/**
		*   Comparison operator.
        *   Compare pointers to proxy objects.
        */
        bool operator == ( const ComPtr<T>& ptr )
        {
            return m_refCounter == ptr.m_refCounter;
        }

		/**
		*   Comparison operator.
        *   Compare pointers to proxy objects.
        */
        bool operator != ( const ComPtr<T>& ptr )
        {
            return m_refCounter != ptr.m_refCounter;
        }

	private:

		/**
		*   Copy operation
		*/
		void Copy(const ComPtr<T>& aptr)
        {
            m_refCounter = aptr.m_refCounter;
            if(m_refCounter != 0)
				m_refCounter->AddRef();
        }

        COMStorage<T>* m_refCounter; ///< Pointer to proxy object.
    };
}
#endif
